﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;

namespace FastEmit
{
    public class FastEmitFactory
    {
        private static readonly object _locker = new();

        private static readonly Dictionary<RuntimeTypeHandle, TypeAccessor> dict = [];

        private static readonly Dictionary<RuntimeTypeHandle, ConstructorInvoker> dictCon = [];

        private static readonly Dictionary<RuntimeTypeHandle, ConstructorAccessor> dictCon2 = [];

#if NET45 || NETSTANDARD2_0 || NET6_0
        private static readonly Dictionary<RuntimeTypeHandle, MemberGetter> dictTask = [];

        private static readonly Dictionary<RuntimeTypeHandle, ValueTaskAccessor> dictValueTask = [];
#endif
        private static readonly Dictionary<RuntimeTypeHandle, MethodAccessor> dictMethod = [];

        public static TypeAccessor GetTypeAccessor(Type type)
        {
            if (!dict.TryGetValue(type.TypeHandle, out var val))
            {
                lock (_locker)
                {
                    if (!dict.ContainsKey(type.TypeHandle))
                    {
                        dict.Add(type.TypeHandle, new TypeAccessor(type));
                    }
                }
                val = dict[type.TypeHandle];
            }
            return val;
        }

        public static ConstructorInvoker GetConstructorInvoker(Type type)
        {
            if (!dictCon.TryGetValue(type.TypeHandle, out var val))
            {
                lock (_locker)
                {
                    if (!dictCon.ContainsKey(type.TypeHandle))
                    {
                        dictCon.Add(type.TypeHandle, ConstructorExtensions.DelegateForCreateInstance(type));
                    }
                }
                val = dictCon[type.TypeHandle];
            }
            return val;
        }

        public static ConstructorAccessor GetConstructorAccessor(Type type)
        {
            if (!dictCon2.TryGetValue(type.TypeHandle, out var val))
            {
                lock (_locker)
                {
                    if (!dictCon2.ContainsKey(type.TypeHandle))
                    {
                        dictCon2.Add(type.TypeHandle, new ConstructorAccessor(type));
                    }
                }
                val = dictCon2[type.TypeHandle];
            }
            return val;
        }

#if NET45 || NETSTANDARD2_0 || NET6_0

        public static MemberGetter GetTaskResultInvoker(Type type)
        {
            if (!dictTask.TryGetValue(type.TypeHandle, out var val))
            {
                lock (_locker)
                {
                    if (!dictTask.ContainsKey(type.TypeHandle))
                    {
                        var invoker = PropertyExtensions.DelegateForGetPropertyValue(type, "Result");
                        dictTask.Add(type.TypeHandle, invoker);
                    }
                }
                val = dictTask[type.TypeHandle];
            }
            return val;
        }

        public static object GetTaskResult(object obj)
        {
            return GetTaskResultInvoker(obj.GetType())(obj);
        }

        public static async Task<object> GetTaskResultAsync(object obj)
        {
            var task = (Task)obj;
            await task;
            return GetTaskResultInvoker(obj.GetType())(task);
        }

        public static ValueTaskAccessor GetValueTaskAccessor(Type type)
        {
            if (!dictValueTask.TryGetValue(type.TypeHandle, out var val))
            {
                lock (_locker)
                {
                    if (!dictValueTask.ContainsKey(type.TypeHandle))
                    {
                        dictValueTask.Add(type.TypeHandle, new ValueTaskAccessor(type));
                    }
                }
                val = dictValueTask[type.TypeHandle];
            }
            return val;
        }

        public static object GetValueTaskResult(object obj)
        {
            return GetValueTaskAccessor(obj.GetType()).GetResult(obj);
        }

        public static Task<object> GetValueTaskResultAsync(object obj)
        {
            return GetValueTaskAccessor(obj.GetType()).GetResultAsync(obj);
        }

# endif
        public static MethodAccessor GetMethodAccessor(Type type)
        {
            if (!dictMethod.TryGetValue(type.TypeHandle, out var val))
            {
                lock (_locker)
                {
                    if (!dictMethod.ContainsKey(type.TypeHandle))
                    {
                        dictMethod.Add(type.TypeHandle, new MethodAccessor(type));
                    }
                }
                val = dictMethod[type.TypeHandle];
            }
            return val;
        }

        #region DataTable

        /// <summary>
        /// 列表转成DataTable
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <returns></returns>
        public static DataTable ListToDataTable<T>(IEnumerable<T> list) where T : class
        {
            var dt = new DataTable();
            var t = typeof(T);
            var proList = t.GetProperties();

            foreach (var pro in proList) //新增列
            {
                dt.Columns.Add(pro.Name, pro.PropertyType);
            }

            var accessor = GetTypeAccessor(t);
            foreach (var item in list) //遍历列表数据
            {
                var row = dt.NewRow();
                foreach (var pro in proList)
                {
                    row[pro.Name] = accessor.GetValue(item, pro.Name);
                }
                dt.Rows.Add(row);
            }
            return dt;
        }

        /// <summary>
        /// DataTable转实体
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static List<T> DataTableToList<T>(DataTable dt) where T : class, new()
        {
            var t = typeof(T);
            var proList = t.GetProperties();
            var list = new List<T>();
            var accessor = GetTypeAccessor(t);
            foreach (DataRow row in dt.Rows)
            {
                var model = new T();
                foreach (var pro in proList)
                {
                    if (!dt.Columns.Contains(pro.Name))
                    {
                        continue;
                    }

                    var val = row[pro.Name];
                    if (val != DBNull.Value)
                    {
                        accessor.SetValue(model, pro.Name, TypeConverter.Get(pro.PropertyType, val));
                    }
                }
                list.Add(model);
            }
            return list;
        }

        #endregion

    }
}
